// ValidationAspects
// Written by Mike Saunders
// Released under Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using ValidationAspects.Core;
using ValidationAspects.Sdk;

namespace ValidationAspects
{
	public static class PropertyValidationRegistry
	{
		private static readonly ValidationRegistry<PropertyInfo> registry = new ValidationRegistry<PropertyInfo>(pi => pi.PropertyType,
		                                                                                                         pi =>
		                                                                                                         ((ValidatorAttribute[])pi.GetCustomAttributes(typeof(ValidatorAttribute), false)).Select(
		                                                                                                         	a => a.Factory));

		private static void EnforceCanValidate(PropertyInfo propertyInfo)
		{
			if (!CanValidate(propertyInfo))
			{
				throw new ValidationException(string.Format("Validation cannot be applied to property [name:{0}] [type:{1}]. Property does not define a non abstract set method.", propertyInfo.Name,
				                                            propertyInfo.DeclaringType));
			}
		}

		public static bool CanValidate(this PropertyInfo propertyInfo)
		{
			Enforce.IsNotNull(propertyInfo, "propertyInfo");
			MethodInfo setMethod = propertyInfo.GetSetMethod(true);
			return setMethod != null && (propertyInfo.DeclaringType.IsInterface ? true : !setMethod.IsAbstract);
		}

		public static void ClearCache()
		{
			registry.ClearCache();
		}

		public static void RevertValidation(this PropertyInfo propertyInfo)
		{
			EnforceCanValidate(propertyInfo);
			registry.RevertValidation(propertyInfo);
		}

		public static void ReplaceValidation(this PropertyInfo propertyInfo, IEnumerable<IValidatorFactory> factories)
		{
			EnforceCanValidate(propertyInfo);
			registry.ReplaceValidation(propertyInfo, factories);
		}

		public static void ReplaceValidation<TValue>(this PropertyInfo propertyInfo, Action<TValue, IValidationContext> validateAction)
		{
			EnforceCanValidate(propertyInfo);
			registry.ReplaceValidation(propertyInfo, validateAction);
		}

		public static void AddValidation(this PropertyInfo propertyInfo, IEnumerable<IValidatorFactory> factories)
		{
			EnforceCanValidate(propertyInfo);
			registry.AddValidation(propertyInfo, factories);
		}

		public static void AddValidation<TValue>(this PropertyInfo propertyInfo, Action<TValue, IValidationContext> validateAction)
		{
			EnforceCanValidate(propertyInfo);
			registry.AddValidation(propertyInfo, validateAction);
		}

		public static IValidator ResolveValidator(this PropertyInfo propertyInfo)
		{
			EnforceCanValidate(propertyInfo);

			try
			{
				return registry.ResolveValidator(propertyInfo);
			}
			catch (ValidationException e)
			{
				throw new ValidationException(string.Format("Failed to construct validator for [type:{0}] [property:{1}].", propertyInfo.DeclaringType, propertyInfo.Name), e);
			}
		}

		public static IEnumerable<TEmit> EmitValidation<TEmit>(this PropertyInfo propertyInfo) where TEmit : class
		{
			EnforceCanValidate(propertyInfo);

			foreach (IValidatorFactory factory in registry.GetFactories(propertyInfo))
			{
				TEmit emit = Emit<TEmit>.From(factory);
				if (emit != null)
					yield return emit;
			}
		}
	}
}